﻿/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "Server/WorldSession.h"
#include "Shared/Log/Log.h"
#include "GlobalVars.h"
#include "Game/Pet.h"
#include "Game/Player.h"
#include "Game/ObjectGuid.h"
#include "Game/DBCStores.h"
#include "Game/ObjectManager.h"
#include "World.h"

class LoginQueryHolder : public SqlQueryHolder
{
private:
    std::uint32_t AccountId_;
    ObjectGuid Guid_;
public:
    LoginQueryHolder(std::uint32_t _AccountId, ObjectGuid _Guid)
        : AccountId_(_AccountId), Guid_(_Guid) { }
    ObjectGuid GetGuid(void) const { return Guid_; }
    std::uint32_t GetAccountId(void) const { return AccountId_; }
    bool Initialize(void);
};

bool LoginQueryHolder::Initialize()
{
    SetSize(MAX_PLAYER_LOGIN_QUERY);

    bool res = true;

    // NOTE: all fields in `characters` must be read to prevent lost character data at next save in case wrong DB structure.
    // !!! NOTE: including unused `zone`,`online`
    res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADFROM, "SELECT "
        "guid, account, name, race, class, gender, level, xp, money, playerBytes, "
        "playerBytes2, playerFlags, position_x, position_y, position_z, map, orientation, "
        "taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, "
        "resettalents_cost, resettalents_time, trans_x, trans_y, trans_z, trans_o, transguid, "
        "extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path,"
        "honor_highest_rank, honor_standing, stored_honor_rating, stored_dishonorable_kills, "
        "stored_honorable_kills, watchedFaction, drunk, health, power1, power2, power3, power4, "
        "power5, exploredZones, equipmentCache, ammoId, actionBars "
        "FROM characters WHERE guid = '%u'", Guid_.GetCounter());

    return res;
}

class CharacterHandler
{
public:
    void HandleCharEnumCallback(QueryResult* _Result, std::uint32_t _Account)
    {
        QueryResultPtr _ResultPtr(_Result);
        WorldSession* _Session = sWorld.FindSession(_Account);
        if (!_Session) {
            return;
        }
        _Session->HandleCharEnum(_Result);
    }
    void HandlePlayerLoginCallback(QueryResult * /*dummy*/, SqlQueryHolder* _Holder)
    {
        AutoPtr<SqlQueryHolder> _HolderPtr(_Holder);
        if (!_HolderPtr) { return; }
        WorldSession* session = sWorld.FindSession(((LoginQueryHolder*)_Holder)->GetAccountId());
        if (!session) {
            return;
        }
        session->HandlePlayerLogin(_Holder);
    }
} G_ChrHandler_;

void WorldSession::HandleCharEnumOpcode(WorldPacket &_Packet)
{
    sLog.outDebug("GameSession::HandleCharEnumOpcode()");

    // 查询帐号角色数据
    sCharaDB.AsyncPQuery(&G_ChrHandler_, &CharacterHandler::HandleCharEnumCallback, GetAccountId(), "SELECT "
        "characters.guid, characters.name, characters.race, characters.class, characters.gender, characters.playerBytes, "
        "characters.playerBytes2, characters.level, characters.zone, characters.map, characters.position_x, "
        "characters.position_y, characters.position_z, guild_member.guildid, characters.playerFlags, "
        "characters.at_login, character_pet.entry, character_pet.modelid, character_pet.level, characters.equipmentCache "
        "FROM characters LEFT JOIN character_pet ON characters.guid=character_pet.owner AND character_pet.slot='%u' "
        "LEFT JOIN guild_member ON characters.guid = guild_member.guid "
        "WHERE characters.account = '%u' ORDER BY characters.guid",
        PET_SAVE_AS_CURRENT, GetAccountId());
}

void WorldSession::HandleCharEnum(QueryResult *_Rs)
{
    WorldPacket _Data(SMSG_CHAR_ENUM, 100);
    std::uint8_t _Num = 0;

    _Data << _Num;
    if (_Rs)
    {
        do
        {
            std::uint32_t _Guidlow = (*_Rs)[0].AsUInt32();
            sLog.outDebug("build enum data for char guid %u from account %u.", _Guidlow, GetAccountId());
            if (Player::BuildCharaEnumData(_Rs, _Data)) {
                ++_Num;
            }
        } while (_Rs->NextRow());
    }

    _Data.Put<std::uint8_t>(0, _Num);
    SendPacket(_Data);
}

void WorldSession::HandleCharCreateOpcode(WorldPacket &_Packet)
{
    std::string _Name;
    std::uint8_t _Race, _Class;
    std::uint8_t _Gender, _Skin, _Face, _HairStyle, _HairColor, _FacialHair, _OutfitId;

    _Packet >> _Name;       // 角色名
    _Packet >> _Race;       // 种族
    _Packet >> _Class;      // 职业
    _Packet >> _Gender;     // 性别
    _Packet >> _Skin;       // 肤色
    _Packet >> _Face;       // 脸型
    _Packet >> _HairStyle;  // 发型
    _Packet >> _HairColor;  // 发色
    _Packet >> _FacialHair; // 胡须
    _Packet >> _OutfitId;   // **

    WorldPacket _RespPkt(SMSG_CHAR_CREATE, 1);

    // 阵营限制检查
    if (GetSecurity() == SEC_PLAYER)
    {
        std::uint32_t _Mask = sWorld.GetConfig(CONFIG_UINT32_CHARACTERS_CREATING_DISABLED);
        if (_Mask != 0)
        {
            bool _Disable = false;
            Team _Team = Player::TeamForRace(_Race);
            switch (_Team)
            {
            case ALLIANCE: _Disable = (_Mask & (1 << 0)) != 0; break;
            case HORDE: _Disable = (_Mask & (1 << 1)) != 0; break;
            default: break;
            }

            if (_Disable) {
                _RespPkt << (std::uint8_t) CHAR_CREATE_DISABLED;
                SendPacket(_RespPkt);
                return;
            }
        }
    }

    // 检查有无种族和职业定义数据
    const struct ChrRacesEntry* _RaceDef = sDBCStores.GetChrRace(_Race);
    const struct ChrClassesEntry* _ClassDef = sDBCStores.GetChrClass(_Class);
    if (!_RaceDef || !_ClassDef) {
        _RespPkt << (std::uint8_t)CHAR_CREATE_FAILED;
        SendPacket(_RespPkt);
        sLog.outError("Class: %u or Race %u not found in DBC (Wrong DBC files?) or Cheater?", 
            _Class, _Race);
        return;
    }

    // 检查角色名
    if (!ObjectManager::NormalizePlayerName(_Name)) {
        _RespPkt << (std::uint8_t)CHAR_NAME_NO_NAME;
        SendPacket(_RespPkt);
        sLog.outError("Account:[%d] but tried to Create character with empty [name]", GetAccountId());
        return;
    }
    std::uint8_t _Res = ObjectManager::CheckPlayerName(_Name, true);
    if (_Res != CHAR_NAME_SUCCESS) {
        _RespPkt << (std::uint8_t)_Res;
        SendPacket(_RespPkt);
        return;
    }
    if (GetSecurity() == SEC_PLAYER && sObjMgr.IsReservedName(_Name)) {
        _RespPkt << (std::uint8_t)CHAR_NAME_RESERVED;
        SendPacket(_RespPkt);
        return;
    }
    if (sObjMgr.GetPlayerGuidByName(_Name)) {
        _RespPkt << (std::uint8_t)CHAR_CREATE_NAME_IN_USE;
        SendPacket(_RespPkt);
        return;
    }

    // TODO：服务器角色创建限制和帐号角色创建限制
    // TODO：双阵营帐号检查（能否在一个帐号上创建两个阵营的角色）

    AutoPtr<Player> _NewChar(new Player(this));
    if (!(_NewChar 
        && _NewChar->Create(sObjMgr.GeneratePlayerLowGuid(), _Name, _Race,
        _Class, _Gender, _Skin, _Face, _HairStyle, _HairColor, _FacialHair, _OutfitId))) {
        _RespPkt << (std::uint8_t)CHAR_CREATE_ERROR;
        SendPacket(_RespPkt);
        return;
    }

    _NewChar->SetAtLoginFlag(AT_LOGIN_FIRST);
    _NewChar->SaveToDB();
    sObjMgr.AddPlayerGuidByName(_Name, _NewChar->GetGUIDLow());

    _RespPkt << (std::uint8_t)CHAR_CREATE_SUCCESS;
    SendPacket(_RespPkt);

    sLog.outBasic("Account: %d (IP: %s) Create Character:[%s] (guid: %u)", GetAccountId(), 
        PeerHost_.c_str(), _Name.c_str(), _NewChar->GetGUIDLow());
    sLog.outChar("Account: %d (IP: %s) Create Character:[%s] (guid: %u)", GetAccountId(), 
        PeerHost_.c_str(), _Name.c_str(), _NewChar->GetGUIDLow());
}

void WorldSession::HandlePlayerLoginOpcode(WorldPacket& _Packet)
{
    ObjectGuid _PlayerGuid;
    _Packet >> _PlayerGuid;

    if (IsPlayerLoading() || GetPlayer() != NULL) {
        sLog.outError("Player tryes to login again, AccountId = %d", GetAccountId());
        return;
    }

    PlayerLoading_ = true;

    sLog.outDebug("WORLD: Received opcode Player Logon Message");

    AutoPtr<LoginQueryHolder> _Holder(new LoginQueryHolder(GetAccountId(), _PlayerGuid));
    if (!_Holder->Initialize()) {
        PlayerLoading_ = false;
        return;
    }

    sCharaDB.DelayQueryHolder(&G_ChrHandler_,
        &CharacterHandler::HandlePlayerLoginCallback, _Holder.release());
}

void WorldSession::HandlePlayerLogin(SqlQueryHolder* _Holder)
{
    LoginQueryHolder* _LoginHolder = (LoginQueryHolder*)_Holder;
}